<?php

/**
 * HotPhpClientModel
 * 
 * 火速PHP客户端抽象类
 *
 * @author Judson-Leigh
 * @version $Id:$
 */
abstract class HotPhpClientModel
{

    /**
     * 命令行提示信息
     *
     * @property string
     */
    protected $_usage = '';
    
    /**
     * 命令行参数配置
     *
     * @property Array
     */
    protected $_params = array(
        0 => array(                     //配置文件参数
            'key' => '-f',              //参数关键字
            'name' => 'Config Name',    //参数名称
            'isMust' => true,           //是否有必须
            'hasValue' => true,         //是否有变量
            'value' => null,            //变量值
        ),
        1 => array(                     //配置文件参数
            'key' => '-q',              //参数关键字
            'name' => 'quit',           //参数名称
            'isMust' => false,          //是否有必须
            'hasValue' => false,        //是否有变量
            'value' => null,            //变量值
        ),
    );
    
    /**
     * 配置信息
     *
     * @property Array
    */
    protected $_config = array();
    
    /**
     * 共享内存句柄
     *
     * @property int
     */
    protected $_shmId = 0;
    
    /**
     * 最大日志池数
     *
     * @property int
     */
    protected $_logSize = 0;
    
    /**
     * 欢迎信息
     *
     * @property string
     */
    public $helloString = '';
    
    /**
     * 命令行提示符
     *
     * @property string
     */
    public $cmdPrompt = '>';
    
    /**
     * 共享内存分区配置
     *
     * @property array
    */
    public static $SHM_CONFIG = array(
        'HEADER_SIZE'       => 6000,    // 头块尺寸（字节）
        'STATUS_NUMBER'     => 100,     // 状态变量最大个数
        'STATUS_FIELD_SIZE' => 50,      // 单状态变量内存大小（字节）
        'LOG_LINE_SIZE'     => 200,     // 单条日志记录内存大小（字节）
    );

    private  $logisinited=false;//是否已完成log初始化

    private $configfile=false;//当前使用的config文件
    /**
     * 构造函数
     *
     * @return HotPhpServerModel 当前对象
    */
    public function __construct() {
    }
    
    /**
     * 获取参数
     *
     * @param array $argv 传递给脚本的参数数组
     * @return bool 是否成功获取参数（null：不需要获取参数）
     */
    public function getParam($argv) {
        if (empty($this->_params) == false && is_array($this->_params)) {
            //循环命令行参数配置
            foreach ($this->_params as $keyParam => $param) {
                //循环传入的参数
                foreach ($argv as $nArgvKey => $strArgvValue) {
                    if ($strArgvValue == $param['key']) {
                        //参数相同
                        if ($param['hasValue'] == true) {
                            //需要获取变量
                            $strParamVal = @$argv[$nArgvKey + 1]; //获取参数变量
                            //判断参数是否输入
                            if ($strParamVal === null || $strParamVal === false || $strParamVal === '' || substr($strParamVal, 0, 1) == '-') {
                                throw new Exception('The ' . $param['name'] . ' [' . $param['key'] . '] parameter is not the input value!');
                            }
                            $this->_params[$keyParam]['value'] = $strParamVal;
                        } else {
                            //不需要获取变量
                            $this->_params[$keyParam]['value'] = true;
                        }
                    }
                }
                //判断必填参数是否输入
                if ($param['isMust'] == true && $this->_params[$keyParam]['value'] == null) {
                    //必填参数未输入
                    throw new Exception('The ' . $param['name'] . ' [' . $param['key'] . '] parameter is not input!');
                }
            }
        } else {
            //不需要获取参数
            return null;
        }
        return true;
    }
    
    /**
     * 取配置文件
     *
     * @param string $configFile 配置文件名
     * @return void
     */
    public function getConfig($configFile) {
        $configFile = trim($configFile);
        //判断配置文件名是否输入
        if (empty($configFile) == true) {
            throw new Exception('The configuration file is null!');
        }
        //判断配置文件名是否存在
        if (is_file($configFile) == false) {
            throw new Exception('The configuration file does not exist!');
        }
    
        //取配置信息
        $arrayIni = @parse_ini_file($configFile);
        if($arrayIni === false || is_array($arrayIni) === false) {
            //取配置错误
            throw new Exception('The configuration file is error!');
        }
        $this->_config = $arrayIni;
        $this->configfile=$configFile;
        return;
    }
    
    /**
     * 初始化日志
     *
     * @param int $logSize 日志池大小（不小于10条，不大于10000条）
     * @param String $serviceFile 服务文件
     * @return void
     */
    public function initLog($logSize) {
        if(!$this->logisinited) {
            $this->logisinited=true;
            $logSize = intval($logSize);
            if ($logSize < 10) {
                $logSize = 10;
            } elseif ($logSize > 10000) {
                $logSize = 10000;
            }
            $this->_logSize = $logSize;

            $shm_key = @ftok($this->configfile, 't');
            if ($shm_key == -1) {
                //取配置错误
                throw new Exception('The service file is error!');
            }

            $shmopSize = ($logSize * self::$SHM_CONFIG['LOG_LINE_SIZE']) + self::$SHM_CONFIG['HEADER_SIZE'] + (self::$SHM_CONFIG['STATUS_NUMBER'] * self::$SHM_CONFIG['STATUS_FIELD_SIZE']);  //共享内存尺寸

            //创建共享内存
            $shm_id = @shmop_open($shm_key, "c", 0666, $shmopSize);

            if ($shm_id === false) {

                $shm_id = @shmop_open($shm_key, "w", 0666, $shmopSize);
                shmop_delete($shm_id);
                $shm_id = @shmop_open($shm_key, "c", 0666, $shmopSize);
            }
            $this->_shmId = $shm_id;
        }
    }
    
    /**
     * 加入日志
     *
     * @param int $logLevel 日志等级
     * @param string $logInfo 日志信息
     * @return bool 是否添加成功
     */
    public function addLog($logLevel, $logInfo) {
        if ($this->_shmId == 0) {
            //日志尚未初始化
            return false;
        }
    
        //取下个日志下标
        $nNextLogNum = $this->getStatusValue('logNex');
    
        //截取前150个字符，合并日志
        $logInfo = date('[y-m-d H:i:s]') . '(L' . $logLevel . ') ' . substr($logInfo,0,150);
    
        shmop_write($this->_shmId, $logInfo, self::$SHM_CONFIG['HEADER_SIZE'] + (self::$SHM_CONFIG['STATUS_NUMBER'] * self::$SHM_CONFIG['STATUS_FIELD_SIZE']) + ($nNextLogNum * self::$SHM_CONFIG['LOG_LINE_SIZE']));
    
        $nNextLogNum ++;
    
        if ($nNextLogNum >= $this->_logSize) {
            $nNextLogNum = 0;
        }
        //写入下个日志下标
        $this->setStatusValue('logNex', $nNextLogNum);
        return true;
    }
    
    /**
     * 获取日志
     *
     * @param int $logNumber 日志数量
     * @return array 日志列表
     */
    public function getLog($logNumber) {
        if ($this->_shmId == 0) {
            //日志尚未初始化
            return false;
        }
        
        $logNumber = intval($logNumber);
        
        if ($logNumber <= 0) {
            //至少取一条记录
            $logNumber = 1;
        }
    
        //取下个日志下标
        $nNextLogNum = $this->getStatusValue('logNex');
        
        $nNowLogNumber = 0;
        
        $arrayReturn = array();
        
        do {
            $nNextLogNum --;
            //传进来的数量可能大于有效日志条数，nNextLogNum=0时，需要从max值向logNex循环
            if ($nNextLogNum < 0) {
                $nNextLogNum=$this->_logSize-1;//lognex到0的遍历
            }
            else if($nNextLogNum==$this->getStatusValue('logNex'))
            {
                break;//max->lognex时，从尾部的遍历已经完成
            }
            
            $strNowLog = trim(shmop_read($this->_shmId,self::$SHM_CONFIG['HEADER_SIZE']
                + (self::$SHM_CONFIG['STATUS_NUMBER'] * self::$SHM_CONFIG['STATUS_FIELD_SIZE'])
                + ($nNextLogNum * self::$SHM_CONFIG['LOG_LINE_SIZE'])
                ,self::$SHM_CONFIG['LOG_LINE_SIZE']));
            if($strNowLog != '') {
                $nNowLogNumber++;
                $arrayReturn[] = '('.$nNowLogNumber.'/' . $nNextLogNum . ')' . $strNowLog;
            }
        } while($nNowLogNumber < $logNumber && $strNowLog != '');
    
        return $arrayReturn;
    }
    
    /**
     * 设置状态变量
     *
     * @param string $key 变量名 (不能超过12个字符)
     * @param string $value 变量值 (不能超过50个字符)
     * @return bool 是否设置成功
     */
    public function setStatusValue($key, $value) {
        if ($this->_shmId == 0) {
            return false;
        }
        $key = trim($key);
        if ($key == '') {
            return false;
        }
        $key = substr($key, 0, 12);
        $value = substr(trim($value), 0, self::$SHM_CONFIG['STATUS_FIELD_SIZE']);
    
        //取出内存头信息
        $strShmHeader = trim(shmop_read($this->_shmId,0,self::$SHM_CONFIG['HEADER_SIZE']));
        if ($strShmHeader == '') {
            $arrayShmHeader = array(
                $key => 0,
            );
        } else {
            $arrayShmHeader = @unserialize($strShmHeader);
            if ($arrayShmHeader == false) {
                $arrayShmHeader = array(
                    $key => 0,
                );
            }
            if (isset($arrayShmHeader[$key]) == false) {
                //当前变量值是否存在
                $arrayShmHeader[$key] = count($arrayShmHeader);
            }
        }
    
        //序列化头数组
        $strShmHeader = serialize($arrayShmHeader);
        $strShmHeader = str_pad($strShmHeader, self::$SHM_CONFIG['HEADER_SIZE']);
    
        //写共享内存头块
        shmop_write($this->_shmId, $strShmHeader, 0);
    
        //写入变量值
        $value = str_pad($value, self::$SHM_CONFIG['STATUS_FIELD_SIZE']);
        shmop_write($this->_shmId, $value, self::$SHM_CONFIG['HEADER_SIZE'] + ($arrayShmHeader[$key] * self::$SHM_CONFIG['STATUS_FIELD_SIZE']));
        return true;
    }
    
    /**
     * 取状态变量
     *
     * @param string $key 变量值 (不能超过12个字符)
     * @return string 状态变量值 （未设置返回NULL）
     */
    public function getStatusValue($key) {
        if ($this->_shmId == 0) {
            return false;
        }
        $key = trim($key);
        if ($key == '') {
            return false;
        }
        $key = substr($key, 0, 12);
   
        //取出内存头信息
        $strShmHeader = trim(shmop_read($this->_shmId,0,self::$SHM_CONFIG['HEADER_SIZE']));

        if ($strShmHeader == '') {
            return null;
        } else {
            $arrayShmHeader = @unserialize($strShmHeader);
            if ($arrayShmHeader == false) {
                return null;
            }
        }
        
        if(isset($arrayShmHeader[$key]) == false) {
            return null;
        }
        $num = (self::$SHM_CONFIG['HEADER_SIZE'] + ($arrayShmHeader[$key] * self::$SHM_CONFIG['STATUS_FIELD_SIZE']));
        //读共享内存状态变量块
        return trim(shmop_read($this->_shmId, self::$SHM_CONFIG['HEADER_SIZE'] + ($arrayShmHeader[$key] * self::$SHM_CONFIG['STATUS_FIELD_SIZE']), self::$SHM_CONFIG['STATUS_FIELD_SIZE']));
    }
    
    /**
     * 取命令行提示信息
     *
     * @return string 命令行提示信息
     */
    public function getUsage() {
        return str_replace('[FILE]', $this->execFile, $this->_usage);
    }
    
    /**
     * 服务初始化（通过重构此方法，实现服务初始化）
     *
     * @return void
     */
    public function init() {
    }
    
    /**
     * 服务运行
     *
     * @return void
     */
    public function run() {
        //验证配置文件
        try {
            $this->getConfig($this->_params[0]['value']);
        } catch (Exception $e) {
            echo $e->getMessage();
            echo $this->getUsage();
            exit;
        }
        //验证进程号文件
        if(isset($this->_config['pid_file']) == false) {
            echo "The configuration file is missing pid_file!\n";
            exit;
        }
        // 日志大小
        if (isset($this->_config['log_size']) == false) {
            echo "The configuration file is missing log_size!\n";
            exit();
        }

        //初始化客户端
        $this->init();
        $this->initLog($this->_config['log_size']);
        
        echo $this->helloString . "\n";
        
        for(;;) {
            //睡眠等待
            usleep(1);
            echo $this->cmdPrompt . ' ';
            //打开标准输入设备
            $stdin = fopen('php://stdin','r');
            //读取100个输入字符
            $strCmd = trim(fgets($stdin,200));
            //关闭输入设备
            fclose($stdin);
            
            //调用解析命令
            $this->client($strCmd);
        }
    }
    
    /**
     * 客户端接口（通过重构此方法，实现客户端业务逻辑）
     * @param string $command 输入的命令
     * @return void
     */
    public function client($command) {
        echo $command . " Hello World! \n";
    }
}

?>